/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		opt reg.cpp
 * PURPOSE:		registry storage user options loading
 *				
 */

namespace RegOptions
{

static const ULONG DefaultStringOpts = 0x80;

template <class T> static BOOL RegQueryBits(T *Dest, HKEY Key, PCWSTR ValueName, UCHAR MaxBits = 32)
{
	T Data;
	ULONG DataLen = sizeof Data;

	if(RegQueryValueEx(Key, ValueName, 0, NULL, c_cast<LPBYTE>(&Data), &DataLen) == ERROR_SUCCESS)
	{
		*Dest = c_cast<T>(Data & (0xFFFFFFFF >> (32 - MaxBits)));
		return TRUE;
	}

	return FALSE;
}

BOOL QueryMultiEnumChoice(HKEY Key, PCWSTR ValueName, SDE_ENUM *Enum)
{
	ULONG ReqLen, Type;
	ULONG Result = RegQueryValueEx(Key, ValueName, 0, &Type, NULL, &ReqLen);

	if(Result != ERROR_SUCCESS)
		return FALSE;

	if(Type != REG_MULTI_SZ)
		return FALSE;

	auto_szpod<WCHAR> MString;
	MString.realloc(ReqLen);
	PWSTR MStringMax = reinterpret_cast<PWSTR>(reinterpret_cast<ULONG_PTR>(MString.get()) + ReqLen);

	Result = RegQueryValueEx(Key, ValueName, 0, NULL, reinterpret_cast<LPBYTE>(MString.get()), &ReqLen);

	if(Result != ERROR_SUCCESS)
		RaiseWin32Error(Result);

	WCHAR Op = L'+'; //+ for bitwise or, - for bitwise and-not, = for replace (default to bitwise or)

	for(PWSTR i = MString; i < MStringMax; i += wcslen(i) + 1)
	{
		if(i[0] == L'\0')
			break; //the double-null at the end of options
		if(i[0] == L'=' && i[1] == '\0')
		{
			Enum->Choice = 0; //clear for replacement
			Op = L'=';
			continue;
		}
		if(i[0] == L'-' && i[1] == '\0')
		{
			Op = L'-';
			continue;
		}
		if(i[0] == L'+' && i[1] == '\0')
		{
			Op = L'+';
			continue;
		}

		for(ULONG j = 0; j < Enum->ValCount; j++)
		{
			SDE_ENUM_VAL *Val = &Enum->Vals[j];

			if(_wcsicmp(i, Val->Name) == 0)
			{
				switch(Op)				
				{
				case L'-':
					Enum->Choice &= ~Val->Value; //'subtract' bits
					break;

				case L'+': //'add' bits
				case L'=': //or accumulate during replacement
					Enum->Choice |= Val->Value;
					break;
				}
				goto continue_outer;
			}
		}
		RaiseTextError(L"Invalid enum value", NULL, i);

		continue_outer:; //i'd use continue, but it can't skip the nested for loop
	}

	return TRUE;
}

BOOL QueryEnumChoice(HKEY Key, PCWSTR ValueName, SDE_ENUM *Enum)
{
	ULONG ReqLen, Type;
	ULONG Result = RegQueryValueEx(Key, ValueName, 0, &Type, NULL, &ReqLen);

	if(Result != ERROR_SUCCESS)
		return FALSE;

	if(Type != REG_SZ)
		return FALSE;

	auto_szpod<WCHAR> String;
	String.realloc(ReqLen);

	Result = RegQueryValueEx(Key, ValueName, 0, NULL, reinterpret_cast<LPBYTE>(String.get()), &ReqLen);

	for(ULONG j = 0; j < Enum->ValCount; j++)
	{
		SDE_ENUM_VAL *Val = &Enum->Vals[j];

		if(_wcsicmp(String, Val->Name) == 0)
		{
			Enum->Choice = Val->Value;
			return TRUE;
		}
	}
	RaiseTextError(L"Invalid enum value", NULL, String);
	return FALSE;
}

void PopulateFromKey(SDE_OPTS *Opts, HKEY Key)
{
	for(ULONG i = 0; i < Opts->OptCount; i++)
	{
		SDE_OPT *Opt = &Opts->Opts[i];

		OpText Ot(NULL, Opt->Name);

		switch(Opt->Type)
		{
			case SptString:
			{
				ULONG ReqLen;
				if(RegQueryValueEx(Key, Opt->Name, 0, NULL, NULL, &ReqLen) != ERROR_SUCCESS)
					continue;

				auto_szpod<WCHAR> StrCpy = auto_szpod_empty(ReqLen);

				RegQueryValueEx(Key, Opt->Name, 0, NULL, c_cast<LPBYTE>(Opt->String), &ReqLen);

				Opt->String = StrCpy.release_unsafe();
				Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED | SDE_OPT_DATA_OWNERSHIP;
				
				continue;
			}

			case SptInt32:
			case SptInt32Hex:
				if(RegQueryBits(&Opt->Int32, Key, Opt->Name, 32))
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
				continue;

			case SptBool:
				if(RegQueryBits(&Opt->Bool, Key, Opt->Name, 1))
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
				continue;

			case SptPresent:
				if(RegQueryValueEx(Key, Opt->Name, 0, NULL, NULL, NULL) == ERROR_SUCCESS)
				{
					Opt->Bool = TRUE;
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
				}
				continue;

			case SptEnum:
				if(QueryEnumChoice(Key, Opt->Name, Opt->Enum))
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
				continue;

			case SptMultiEnumOr:
				if(QueryMultiEnumChoice(Key, Opt->Name, Opt->Enum))
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
				continue;

			case SptSubList:
				if(Opt->SubList)
				{
					HKEY SubKey;
					LONG Result;

					Result = RegOpenKeyEx(Key, Opt->Name, 0, KEY_QUERY_VALUE, &SubKey);

					if(Result == ERROR_SUCCESS)
					{
						PopulateFromKey(Opt->SubList, SubKey);
						RegCloseKey(SubKey);
					}
				}
				continue;

			case SptOther:
			default:
				continue;
		}

		Ot;
	}
}

void Populate(SDE_OPTS *Opts, PCWSTR KeyName = L"SOFTWARE\\SDEdit")
{
	HKEY Key;
	LONG Result;

	Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, KeyName, 0, KEY_QUERY_VALUE, &Key);
	if(Result == ERROR_SUCCESS)
	{
		PopulateFromKey(Opts, Key);
		RegCloseKey(Key);
	}

	Result = RegOpenKeyEx(HKEY_CURRENT_USER, KeyName, 0, KEY_QUERY_VALUE, &Key);
	if(Result == ERROR_SUCCESS)
	{
		PopulateFromKey(Opts, Key);
		RegCloseKey(Key);
	}
}

}//namespace RegOptions